package com.commons.dbutils.util;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.commons.dbutils.annotation.Column;
import com.commons.dbutils.annotation.Id;
import com.commons.dbutils.annotation.NotDBColumn;
import com.commons.dbutils.annotation.Table;

public class ClassInfo {

	private Map<String, Field> mapIDField;
	private Map<String, Field> mapAllDBField;
	private Map<String, Field> mapInsertableField;
	private Map<String, Field> mapUpdatableField;
	private Map<String, Field> mapIdentityField;
	private Map<String, String> mapDBColumnName;
	private Map<String, Method> mapSetMethod;
	private Map<String, Method> mapGetMethod;
	private String tableName;

	public ClassInfo(Class<?> clazz) throws Exception {
		setTableName(getTableName(clazz));
		setMapIDField(getIdFields(clazz));
		setMapAllDBField(getAllDBFields(clazz));
		setMapInsertableField(getInsertableFields(clazz));
		setMapUpdatableField(getUpdatableFields(clazz));
		setMapDBColumnName(getCloumnName(clazz));
		setMapSetMethod(getSetterMethod(clazz));
		setMapGetMethod(getGetterMethod(clazz));
		setMapIdentityField(getIdentityFields(clazz));
	}

	private String getTableName(Class<?> clazz) {
		if (clazz.isAnnotationPresent(Table.class)) {
			Table table = clazz.getAnnotation(Table.class);
			if (!table.name().equalsIgnoreCase("className")) {
				return table.name();
			}
		}
		String name = clazz.getName();
		return name.substring(name.lastIndexOf(".") + 1);
	}

	private Map<String, Field> getInsertableFields(Class<?> clazz) {
		
		Field[] fields = clazz.getDeclaredFields();
		if(fields==null || fields.length<1 ){
			return null;
		}
		Map<String,Field> mapFfields = new HashMap<String,Field>(fields.length);
		Id id =null;
		Column column=null;
		for (Field f : fields) {
			if (!f.isAnnotationPresent(NotDBColumn.class)) {
				if (f.isAnnotationPresent(Id.class)) {
					id = f.getAnnotation(Id.class);
					if (id.insertable())
						mapFfields.put(f.getName(), f);
				} else {
					column = f.getAnnotation(Column.class);
					if (column != null) {
						if (!column.defaultDBValue())
							mapFfields.put(f.getName(), f);
					} else {
						mapFfields.put(f.getName(), f);
					}
				}
			}
		}
		return mapFfields;
	}

	private Map<String, Field> getAllDBFields(Class<?> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		if(fields==null || fields.length<1){
			return null;
		}
		Map<String,Field> mapFfields = new HashMap<String,Field>(fields.length);
		for (Field f : fields) {
			if (f.isAnnotationPresent(NotDBColumn.class)
					|| "serialVersionUID".equalsIgnoreCase(f.getName())){
				continue;
			}
			mapFfields.put(f.getName(), f);
		}
		
		return mapFfields;
	}

	private Map<String, Field> getIdFields(Class<?> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		if(fields==null || fields.length<1){
			return null;
		}
		Map<String,Field> mapFfields = new HashMap<String,Field>(fields.length);
		for (Field f : fields) {
			if (f.isAnnotationPresent(Id.class)) {
				mapFfields.put(f.getName(), f);
			}
		}
		return mapFfields;
	}

	private Map<String, Field> getUpdatableFields(Class<?> clazz) {
		
		Field[] fields = clazz.getDeclaredFields();
		if(fields==null || fields.length<1){
			return null;
		}
		Map<String,Field> mapFfields = new HashMap<String,Field>(fields.length);
		Id id=null;
		Column column=null;
		for (Field f : fields) {
			if (!f.isAnnotationPresent(NotDBColumn.class)) {
				if (f.isAnnotationPresent(Id.class)) {
					id = f.getAnnotation(Id.class);
					if (id.updatable())
						mapFfields.put(f.getName(), f);
				} else {
					column = f.getAnnotation(Column.class);
					if (column != null) {
						if (!column.defaultDBValue())
							mapFfields.put(f.getName(), f);
					} else {
						mapFfields.put(f.getName(), f);
					}
				}
			}
		}
		return mapFfields;
	}

	private Map<String, String> getCloumnName(Class<?> clazz) {
		if(this.mapAllDBField==null || this.mapAllDBField.size()<1){
			return null;
		}
		Collection<Field> fList = this.mapAllDBField.values();
		
		Map<String,String> mapNames = new HashMap<String,String>(fList.size());
		Column column=null;
		for (Field f : fList) {
			if (f.isAnnotationPresent(Column.class)) {
				column = f.getAnnotation(Column.class);
				if (!column.name().equalsIgnoreCase("fieldName")) {
					mapNames.put(f.getName(), column.name());
				} else
					mapNames.put(f.getName(), f.getName());
			} else {
				mapNames.put(f.getName(), f.getName());
			}
		}
		return mapNames;
	}

	private Map<String, Method> getSetterMethod(Class<?> clazz)
			throws Exception {
		if(this.mapAllDBField==null || this.mapAllDBField.size()<1){
			return null;
		}
		Collection<Field> fList = this.mapAllDBField.values();
		Map<String, Method> mapMethod = new HashMap<String, Method>(fList.size());
		PropertyDescriptor[] lPropDesc = Introspector.getBeanInfo(clazz, 1)
				.getPropertyDescriptors();
		Method setMethod=null;
		for (Field f : fList) {
			for (PropertyDescriptor aLPropDesc : lPropDesc) {
				if (aLPropDesc.getName().equalsIgnoreCase(f.getName())) {
					setMethod = aLPropDesc.getWriteMethod();
					mapMethod.put(f.getName(), setMethod);
					break;
				}
			}
		}
		Column column=null;
		Method[] arrayOfMethod=clazz.getMethods();
		Method m=null,setterMethod=null;
		String setFunName = null;
		for (Field f : fList) {
			setterMethod = mapMethod.get(f.getName());
			if (setterMethod == null) {
				
				if (f.isAnnotationPresent(Column.class)) {
					column = f.getAnnotation(Column.class);
					if (!column.setFuncName().equalsIgnoreCase("setField")) {
						setFunName = column.setFuncName();
					} else
						setFunName = new StringBuffer("set")
									.append(f.getName().substring(0, 1).toUpperCase())
									.append(f.getName().substring(1)).toString();
				} else {
					setFunName = new StringBuffer("set")
								.append(f.getName().substring(0, 1).toUpperCase())
								.append(f.getName().substring(1)).toString();
				}
				
				
				for (int localMethod1 = 0; localMethod1 < arrayOfMethod.length; localMethod1++) {
					m = arrayOfMethod[localMethod1];
					if (m.getName().equals(setFunName)) {
						setterMethod = m;
						break;
					}
				}

				mapMethod.put(f.getName(), setterMethod);
			}
		}

		for (Field f : fList) {
			setterMethod = mapMethod.get(f.getName());
			if (setterMethod == null) {
				throw new Exception(new StringBuffer("can't find set method field:")
									.append(f.getName()).append("  class:")
									.append(clazz.getName()).toString());
			}
		}

		return mapMethod;
	}

	private Map<String, Method> getGetterMethod(Class<?> clazz)
			throws Exception {
		if(this.mapAllDBField==null || this.mapAllDBField.size()<1){
			return null;
		}
		Collection<Field> fList = this.mapAllDBField.values();
		Map<String, Method> mapMethod = new HashMap<String, Method>(fList.size());
		PropertyDescriptor[] lPropDesc = Introspector.getBeanInfo(clazz, 1)
				.getPropertyDescriptors();
		Method getMethod=null;
		for (Field f : fList) {
			for (PropertyDescriptor aLPropDesc : lPropDesc) {
				if (aLPropDesc.getName().equalsIgnoreCase(f.getName())) {
					getMethod = aLPropDesc.getReadMethod();
					mapMethod.put(f.getName(), getMethod);
					break;
				}
			}
		}
		String getFunName = null;
		Column column=null;
		Method getterMethod=null,m=null;
		Method[] arrayOfMethod = clazz.getMethods();
		int methodsLen = arrayOfMethod.length;
		for (Field f : fList) {
			getterMethod = mapMethod.get(f.getName());
			
			if (f.isAnnotationPresent(Column.class)) {
				column =  f.getAnnotation(Column.class);
				if (!column.getFuncName().equalsIgnoreCase("getField")) {
					getFunName = column.getFuncName();
				} else
					getFunName = new StringBuffer("get").append(f.getName().substring(0, 1).toUpperCase())
									.append(f.getName().substring(1)).toString();
			} else {
				getFunName = new StringBuffer("get").append(f.getName().substring(0, 1).toUpperCase())
						.append(f.getName().substring(1)).toString();
			}
			
			
			for (int localMethod1 = 0; localMethod1 < methodsLen; localMethod1++) {
				m = arrayOfMethod[localMethod1];
				if (m.getName().equals(getFunName)) {
					getterMethod = m;
					break;
				}
			}
			mapMethod.put(f.getName(), getterMethod);
		}
		
		for (Field f : fList) {
			getterMethod = mapMethod.get(f.getName());
			if (getterMethod == null) {
				throw new Exception(new StringBuffer("can't find get method field:").append(f.getName())
							.append("  class:").append(clazz.getName()).toString());
			}
		}

		return mapMethod;
	}


	private Map<String, Field> getIdentityFields(Class<?> clazz) {
		
		Field[] fields = clazz.getDeclaredFields();
		if(fields==null || fields.length<1){
			return null;
		}
		Map<String, Field> mapField = new HashMap<String, Field>(fields.length);
		Id id=null;
		for (Field f : fields) {
			if (f.isAnnotationPresent(Id.class)) {
				id = (Id) f.getAnnotation(Id.class);
				if ((!id.insertable()) && (!id.updatable())) {
					mapField.put(f.getName(), f);
				}
			}
		}
		return mapField;
	}

	public Map<String, Field> getMapIDField() {
		return this.mapIDField;
	}

	public void setMapIDField(Map<String, Field> mapIDField) {
		this.mapIDField = mapIDField;
	}

	public Map<String, Field> getMapAllDBField() {
		return this.mapAllDBField;
	}

	public void setMapAllDBField(Map<String, Field> mapAllDBField) {
		this.mapAllDBField = mapAllDBField;
	}

	public Map<String, Field> getMapInsertableField() {
		return this.mapInsertableField;
	}

	public void setMapInsertableField(Map<String, Field> mapInsertableField) {
		this.mapInsertableField = mapInsertableField;
	}

	public Map<String, Field> getMapUpdatableField() {
		return this.mapUpdatableField;
	}

	public void setMapUpdatableField(Map<String, Field> mapUpdatableField) {
		this.mapUpdatableField = mapUpdatableField;
	}

	public Map<String, String> getMapDBColumnName() {
		return this.mapDBColumnName;
	}

	public void setMapDBColumnName(Map<String, String> mapDBColumnName) {
		this.mapDBColumnName = mapDBColumnName;
	}

	public Map<String, Method> getMapSetMethod() {
		return this.mapSetMethod;
	}

	public void setMapSetMethod(Map<String, Method> mapSetMethod) {
		this.mapSetMethod = mapSetMethod;
	}

	public Map<String, Method> getMapGetMethod() {
		return this.mapGetMethod;
	}

	public void setMapGetMethod(Map<String, Method> mapGetMethod) {
		this.mapGetMethod = mapGetMethod;
	}

	public String getTableName() {
		return this.tableName;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	

	public void setMapIdentityField(Map<String, Field> mapIdentityField) {
		this.mapIdentityField = mapIdentityField;
	}

	public Map<String, Field> getMapIdentityField() {
		return this.mapIdentityField;
	}

}
